﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Proggy.Web.Cms.Membership;
using System.Security.Principal;
using System.Web.Configuration;
using Proggy.Web.Cms.Context;
using Proggy.Data.Abstract;
using Proggy.Data.Entities;
using Proggy.Web.Cms.ViewModels;
using System.Net.Mail;
using Proggy.Web.Cms.Service;

namespace Proggy.Web.Cms.Repositories.Membership
{
    /// <summary>
    /// Concrete repository for membership
    /// </summary>
    public class MembershipRepository : IMembershipRepository
    {
        public MembershipRepository(INodeRepository nodeRepo, IMembershipDataRepository membDataRepo, IEmailService emailService)
        {
            this._membRepo = membDataRepo;
            this._nodeRepo = nodeRepo;
            this._emailService = emailService;
        }

        private INodeRepository _nodeRepo;
        private IMembershipDataRepository _membRepo;
        private IEmailService _emailService;

        /// <summary>
        /// Get member from IPrinciple object
        /// </summary>
        /// <param name="iPrincipal"></param>
        /// <param name="activeOnly"></param>
        /// <returns></returns>
        public ProggyMember GetMember(IPrincipal iPrincipal, bool activeOnly)
        {
            // check
            if (iPrincipal == null || iPrincipal.Identity == null || !iPrincipal.Identity.IsAuthenticated)
            {
                return null;
            }

            return this.getMember(iPrincipal.Identity.Name, activeOnly);
        }

        /// <summary>
        /// Returns proggy member object from username
        /// </summary>
        /// <param name="username"></param>
        /// <param name="activeOnly"></param>
        /// <returns></returns>
        public ProggyMember GetMember(string username, bool activeOnly)
        {
            // check            
            if (string.IsNullOrEmpty(username))
            {
                return null;
            }

            //
            return this.getMember(username, activeOnly);
        }

        /// <summary>
        /// Get member from username
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        private ProggyMember getMember(string username, bool activeOnly)
        {
            // create
            var member = new ProggyMember();

            // set auth mode
            member.AuthenticationMode = ConfigAuthenticationSection.Mode;

            // set username
            member.Username = username;

            // set group
            member.Groups = this.GetProggyGroupsForMember(member.Username);

            //check
            if (MembershipContext.CurrentAuthenticationMode == AuthenticationMode.Windows)
            {
                // set member via ad
                this.setRemainingMemberPropertiesViaAd(ref member);
            }
            else if (MembershipContext.CurrentAuthenticationMode == AuthenticationMode.Forms)
            {
                // set member via forms
                this.setRemainingMemberPropertiesViaForms(ref member, activeOnly);
            }
            else
            {
                throw new Exception("Only Windows or Forms authentication are allowed.");
            }

            //
            return member;
        }

        /// <summary>
        /// Set properties via AD
        /// </summary>
        /// <param name="member"></param>
        private void setRemainingMemberPropertiesViaAd(ref ProggyMember member)
        {
            throw new NotImplementedException("Windows authentication not complete for this release");
        }

        /// <summary>
        /// Set props
        /// </summary>
        /// <param name="member"></param>
        private void setRemainingMemberPropertiesViaForms(ref ProggyMember member, bool activeOnly)
        {
            // get forms member
            var formsMember = this._membRepo.GetFormsAuthenticationMemberFromUsername(member.Username, activeOnly);

            // check
            if (formsMember != null)
            {
                // set
                member.EmailAddress = formsMember.Email;
                member.Name = formsMember.Name;
            }
            else
            {
                member = null;
            }
        }

        /// <summary>
        /// Gets authentication section from web.config
        /// </summary>
        /// <returns></returns>
        public AuthenticationSection ConfigAuthenticationSection
        {
            get
            {
                //
                return MembershipContext.ConfigAuthenticationSection;
            }
        }

        /// <summary>
        /// Checks groups against current user
        /// </summary>
        /// <param name="proggyMember"></param>
        /// <param name="groups"></param>
        /// <returns></returns>
        public bool GetAuthOk(ProggyMember proggyMember, string[] groups)
        {
            // get groups list for member
            var memberGroupsList = proggyMember.Groups.Select(x => x.GroupName.ToLower()).ToList();

            // check for global - if present, EVERYTHING IS FINE!!
            if (memberGroupsList.Count > 0 && memberGroupsList.Contains(MembershipConstants.GLOBAL_ADMIN_GROUP_NAME.ToLower()))
            {
                return true;
            }


            // check for back end user - if not, return false
            if (memberGroupsList.Count < 1 || !memberGroupsList.Contains(MembershipConstants.BACK_END_USER_GROUP_NAME.ToLower()))
            {
                return false;
            }






            // get required groups
            var requiredGroupsList = groups.Select(x => x.ToLower()).ToList();

            // check through groups required for match
            foreach (var group in requiredGroupsList)
            {
                // throw false if members groups doesn't contain required
                if (!memberGroupsList.Contains(group))
                {
                    return false;
                }
            }

            // got this far so all must be well!!
            return true;
        }

        /// <summary>
        /// Returns groups for member
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        public IEnumerable<Group> GetProggyGroupsForMember(string username)
        {
            return this._membRepo.GetGroupsForMember(username);
        }

        /// <summary>
        /// Returns true if user authenticated ok
        /// </summary>
        /// <param name="model"></param>
        /// <param name="setLastLogon"></param>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        public bool GetFormsAuthenticationOk(ProggyFormsAuthenticationLogonViewModel model, bool setLastLogon, ref string errorMsg)
        {
            // get error msg
            errorMsg = "The credentials you have supplied are incorrect";

            // get user
            var member = this._membRepo.GetFormsAuthenticationMemberFromUsername(model.Username, true);

            // check
            if (member != null)
            {
                // set password in member
                member.EnteredPassword = model.Password;

                //
                if (member.ValidatePassword())
                {
                    if (setLastLogon)
                    {
                        this._membRepo.SetLastLogon(member);
                    };

                    return true;
                }
            }

            // error message already set
            return false;

        }

        /// <summary>
        /// Returns view model
        /// </summary>
        /// <returns></returns>
        public ChangePasswordViewModel GetChangePasswordViewModel()
        {
            return new ChangePasswordViewModel();
        }

        /// <summary>
        /// Changes password, if valid
        /// </summary>
        /// <param name="model"></param>
        /// <param name="currentUsername"></param>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        public bool GetChangePasswordOk(ChangePasswordViewModel model, string currentUsername, ref string errorMsg)
        {
            // check same
            if (model.NewPassword1 != model.NewPassword2)
            {
                errorMsg = "Passwords must match";
                return false;
            }

            // check data repo method
            if (!this._membRepo.GetChangePasswordOk(currentUsername, model.CurrentPassword, model.NewPassword1, ref errorMsg))
            {
                return false;
            }

            // check again
            return
                this.GetFormsAuthenticationOk(
                    new ProggyFormsAuthenticationLogonViewModel
                    {
                        Password = model.NewPassword1,
                        Username = currentUsername
                    },
                    false,
                    ref errorMsg
                );
        }

        /// <summary>
        /// Returns view model
        /// </summary>
        /// <returns></returns>
        public RegisterViewModel GetRegisterViewModel()
        {
            return new RegisterViewModel();
        }

        /// <summary>
        /// Register new member and send activation email
        /// </summary>
        /// <param name="model"></param>
        /// <param name="activationUrl"></param>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        public bool GetRegisterNewMemberOk(RegisterViewModel model, string activationUrl, ref string errorMsg)
        {
            try
            {
                // check same
                if (model.Password1 != model.Password2)
                {
                    errorMsg = "Passwords must match";
                    return false;
                }

                // get guid
                var guid = Guid.NewGuid();

                // check data repo method
                if (!this._membRepo.GetRegisterNewMemberOk(false, model.Name, model.Username, model.Email, model.Password1, guid, ref errorMsg))
                {
                    return false;
                }

                // try send
                if (!this.SendActivationEmail(model.Email, activationUrl, guid, ref errorMsg))
                {
                    return false;
                }

                // save data repo method
                if (!this._membRepo.GetRegisterNewMemberOk(true, model.Name, model.Username, model.Email, model.Password1, guid, ref errorMsg))
                {
                    return false;
                }



                // 
                return true;

            }
            catch (Exception ex)
            {
                errorMsg = ex.Message;
                return false;
            }

        }

        /// <summary>
        /// Send activation email
        /// </summary>
        /// <param name="email"></param>
        /// <param name="activationUrl"></param>
        /// <param name="guid"></param>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        public bool SendActivationEmail(string email, string activationUrl, Guid guid, ref string errorMsg)
        {
            try
            {
                // get email details
                var subject = "Activate your membership to " + this._emailService.EmailConfiguration.SiteName;
                var url = activationUrl + "?g=ACTIVATION_" + guid.ToString();
                var body =
                    "<p>Click here to activate your membership - <a href=\"" + url + "\">" + url + "</a></p>";
                var to = email;
                var isHtml = true;

                // send
                return this._emailService.SendBasicEmail(isHtml, subject, body, to, ref errorMsg);
            }
            catch (Exception ex)
            {
                errorMsg = string.IsNullOrEmpty(errorMsg) ? ex.Message : errorMsg;
                return false;
            }
        }

        /// <summary>
        /// Activate membership
        /// </summary>
        /// <param name="activationCode"></param>
        public void ActivateMembership(string activationCode)
        {
            this._membRepo.ActivateMembership(activationCode);
        }

        /// <summary>
        /// Forgotten details view model
        /// </summary>
        /// <returns></returns>
        public ForgotViewModel GetForgotViewModel() { return new ForgotViewModel(); }

        /// <summary>
        /// Reset password and send details
        /// </summary>
        /// <param name="model"></param>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        public bool GetResendDetailsOk(ForgotViewModel model, ref string errorMsg)
        {

            // start member
            var username = string.Empty;
            var newPassword = Guid.NewGuid().ToString();

            //
            if (this._membRepo.GetResetPasswordOk(model.EmailAddress, newPassword, ref username, ref errorMsg))
            {

                // get email details
                var subject = "Account details for " + this._emailService.EmailConfiguration.SiteName;
                var body =
                    "<h1>ACCOUNT DETAILS</h1>" +
                    "<h2>Username</h2>" +
                    "<p>" + username + "</p>" +
                    "<h2>New password</h2>" +
                    "<p>" + newPassword + "</p>";
                var to = model.EmailAddress;

                // try send
                if (this._emailService.SendBasicEmail(true, subject, body, to, ref errorMsg))
                {
                    return true;
                }
            }

            //
            return false;

        }

        /// <summary>
        /// Logon view model
        /// </summary>
        /// <param name="returnUrl"></param>
        /// <returns></returns>
        public ProggyFormsAuthenticationLogonViewModel GetLogonViewModel(string returnUrl)
        {
            return new ProggyFormsAuthenticationLogonViewModel { ReturnUrl = returnUrl };
        }

        /// <summary>
        /// Get all groups
        /// </summary>
        /// <returns></returns>
        public IQueryable<Group> GetGroups()
        {
            //
            return this._membRepo.Groups;
        }

        /// <summary>
        /// GEt all members
        /// </summary>
        /// <param name="activeOnly"></param>
        /// <returns></returns>
        public IQueryable<ProggyMember> GetAllMembers(bool activeOnly)
        {
            // list
            var output = new List<ProggyMember>();
            var usernames = new List<string>();

            // get usernames
            if (MembershipContext.CurrentAuthenticationMode == AuthenticationMode.Forms)
            {
                // get members
                usernames = (activeOnly ?
                    this._membRepo.FormsAuthenticationMembers
                    .Where(x => x.IsActive)
                    .Select(x => x.Username)
                    .ToList()
                    :
                    this._membRepo.FormsAuthenticationMembers
                    .Select(x => x.Username)
                    .ToList());
            }
            else if (MembershipContext.CurrentAuthenticationMode == AuthenticationMode.Windows)
            {
                throw new Exception("WINDOWS AUTHENTICATION CODE NOT COMPLETE FOR THIS RELEASE");
            }
            else
            {
                throw new Exception("Only windows or forms authentication is allowed");
            }

            // set list
            usernames.ForEach(x => output.Add(this.GetMember(x, activeOnly)));

            //
            return output.OrderBy(x => x.Name).AsQueryable();
        }

        /// <summary>
        /// Return group member
        /// </summary>
        /// <param name="groupId"></param>
        /// <returns></returns>
        public AddGroupMembersViewModel GetAddGroupMemberViewModel(int groupId)
        {
            // get group
            var group = this._membRepo.Groups.SingleOrDefault(x => x.GroupId == groupId);

            // check null
            if (group == null)
            {
                return null;
            }

            // get members already present
            var membersAlready = group.GroupMembers.Select(x => x.Username).ToArray();

            // get available members
            var allMembers = this.GetAllMembers(false);
            var availableMembers = this.GetAllMembers(false).Where(x => !membersAlready.Contains(x.Username)).ToArray();

            // create new
            var viewModel = new AddGroupMembersViewModel
            {
                Group = group,
                MembersAvailable = availableMembers.Select(x => new ProggyMemberAdminHelperModel
                {
                    AuthenticationMode = x.AuthenticationMode,
                    EmailAddress = x.EmailAddress,
                    Groups = x.Groups,
                    Name = x.Name,
                    Selected = false,
                    Username = x.Username

                }).OrderBy(x => x.Name).ToArray()
            };

            //
            return viewModel;
        }

        /// <summary>
        /// Add members
        /// </summary>
        /// <param name="model"></param>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        public bool GetAddMembersOk(AddGroupMembersViewModel model, string currentUsername, ref string errorMsg)
        {
            // get added
            var added = model.MembersAvailable.Where(x => x.Selected).Select(x => x.Username).ToList();

            // add
            foreach (var memb in added)
            {
                //
                if (!this._membRepo.AddMemberToGroup(model.Group, memb, currentUsername, ref errorMsg))
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Remove group member
        /// </summary>
        /// <param name="groupMemberId"></param>
        public void RemoveGroupMember(int groupMemberId)
        {
            this._membRepo.RemoveGroupMember(groupMemberId);
        }

        /// <summary>
        /// Get group view model
        /// </summary>
        /// <param name="groupId"></param>
        /// <returns></returns>
        public GroupViewModel GetGroupViewModel(int groupId)
        {
            // get group
            var group = this.GetGroups().SingleOrDefault(x => x.GroupId == groupId);

            //
            if (group != null)
            {
                // get members
                var members = new List<ProggyMemberAdminHelperModel>();

                // iterate
                foreach (var member in group.GroupMembers)
                {
                    // get member
                    var pMember = this.GetMember(member.Username, false);

                    //
                    if (pMember != null)
                    {
                        // add
                        members.Add(new ProggyMemberAdminHelperModel
                        {
                            AuthenticationMode = pMember.AuthenticationMode,
                            EmailAddress = pMember.EmailAddress,
                            GroupMemberId = member.GroupMemberId,
                            Name = pMember.Name,
                            Username = pMember.Username
                        });
                    }
                }

                return new GroupViewModel
                {
                    Group = group,
                    Members = members
                };
            }

            return null;


        }

        /// <summary>
        /// Add group
        /// </summary>
        /// <param name="groupName"></param>
        /// <param name="errorMsg"></param>
        /// <returns></returns>
        public bool GetAddGroupOk(string groupName, string currentUsername, ref string errorMsg)
        {
            return this._membRepo.AddGroup(groupName, currentUsername, ref errorMsg);
        }
    }
}
